#import "AYBLEManager.h"
#import <pthread.h>
#import "AYBluetooth.h"
#import "AYBLETimer.h"
#import "AYBLEDevice.h"
#import "NSData+AYExtension.h"
#include "ay_xxtea.h"

#define H2B16(v) CFSwapInt16HostToBig(v)
#define B2H16(v) CFSwapInt16BigToHost(v)

void AYBLELogger(NSInteger level,NSString *format,...) {
    if (SharedBLEManager.logCallback) {
        va_list args;
        va_start(args, format);
        NSString *log = [[NSString alloc] initWithFormat:format arguments:args];
        va_end(args);
        SharedBLEManager.logCallback(level,[NSString stringWithFormat:@"🔵: %@",log]);
    }
}

@implementation AYBLEPacket

+ (instancetype)packetWithCommand:(AYBLECommandType)command isTimeout:(BOOL)isTimeout data:(NSData *)data {
    AYBLEPacket *resp = [[self alloc] init];
    resp.command = command;
    resp.isTimeout = isTimeout;
    resp.data = data;
    if (data.length < 12) {
        resp.message = nil;
    } else {
        NSData *decryptData = [SharedBLEManager xxteaDecrypt:data];
        resp.message = [NSJSONSerialization JSONObjectWithData:decryptData options:kNilOptions error:nil];
    }
    AYBLELogger(2,@"packet command:%04lX, data:%@", (unsigned long)command, resp.message?:@"NULL");
    return resp;
}


@end
@interface AYBLEManager()

@property (nonatomic, strong) AYBluetooth *bluetooth;
@property (nonatomic, strong) CBCharacteristic *notifyCharc; ///< 读取特征
@property (nonatomic, strong) CBCharacteristic *writeCharc; ///< 写入特征
@property (nonatomic, strong, nullable) AYBLEDevice *device;

/// 存储从只读特征解析到的数据
@property (nonatomic, strong) NSMutableData *packetData;
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, AYBLETimer *> *commandTimers;
@property (nonatomic, strong) NSMutableDictionary *commandResponses;
@property (nonatomic, strong) NSMutableDictionary *commandSequences;
@property (nonatomic, copy, nullable) NSString *mac;
@property (nonatomic, copy, readonly) NSString *readUUID;
@property (nonatomic, copy, readonly) NSString *writeUUID;
@property (nonatomic, assign, readonly) uint16_t syncCode;
@property (nonatomic, assign) AYBLECommandType sendedCommand;
@property (nonatomic, copy) NSArray<NSString *> *prefixes;
@property (nonatomic, assign) BOOL isConnecting;
@end

@implementation AYBLEManager


- (NSString *)readUUID {
    return @"FF06";
}

- (NSString *)writeUUID {
    return @"FF05";
}

- (uint16_t)syncCode {
    return 0x0A59;
}

- (CBManagerState)bleState {
    return _bluetooth.centralManager.state;
}

- (BOOL)isConnected {
    return self.device.peripheral.state == CBPeripheralStateConnected;
}

+ (instancetype)sharedManager {
    static dispatch_once_t onceToken;
    static AYBLEManager *_instance;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}


- (instancetype)init
{
    self = [super init];
    if (self) {
        _bluetooth = [AYBluetooth sharedBluetooth];
        _packetData = [NSMutableData data];
        _commandResponses = [NSMutableDictionary dictionary];
        _commandTimers = [NSMutableDictionary dictionary];
        _commandSequences = [NSMutableDictionary dictionary];
        [self initDelegate];
    }
    return self;
}

- (void)initDelegate {
    __weak typeof(self) weakSelf = self;
    [_bluetooth setBlockOnCentralManagerDidUpdateState:^(CBCentralManager *central) {
        AYBLELogger(2,@"ble update state:%zd",central.state);
        [[NSNotificationCenter defaultCenter] postNotificationName:bleCentralStateDidUpdateNotification object:@(central.state)];
    }];
    //设备名称过滤器
    [_bluetooth setFilterOnDiscoverPeripherals:^BOOL(NSString *peripheralName, NSDictionary *advertisementData, NSNumber *RSSI) {
        return [weakSelf.prefixes indexOfObjectPassingTest:^BOOL(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            return [advertisementData[CBAdvertisementDataLocalNameKey] hasPrefix:obj];
        }] != NSNotFound;
    }];
    [_bluetooth setBlockOnDiscoverToPeripherals:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) { // 扫描到设备的委托
        AYBLEDevice *device = [[AYBLEDevice alloc] initWithAdvertisementData:advertisementData peripheral:peripheral prefixes:weakSelf.prefixes];
        AYBLELogger(2,@"discover peripheral:%@",peripheral);
        [[NSNotificationCenter defaultCenter] postNotificationName:blePeripheralDiscoverNotification object:device userInfo:nil];
        NSString *bleMac = [weakSelf.mac stringByReplacingOccurrencesOfString:weakSelf.prefixes.firstObject withString:@""].lowercaseString;
        if (weakSelf.mac && [device.mac isEqualToString:bleMac]) {
            weakSelf.mac = nil;
            weakSelf.device = device;
            [weakSelf connectPeripheral:peripheral]; // 连接外设
        }
    }];
    [_bluetooth setBlockOnCancelAllPeripheralsConnectionBlock:^(CBCentralManager *centralManager) {
        NSLog(@"setBlockOnCancelAllPeripheralsConnectionBlock");
    }];
    [_bluetooth setBlockOnCancelScanBlock:^(CBCentralManager *centralManager) {
        
    }];
    [_bluetooth setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral) { // 连接外设 成功
        AYBLELogger(2,@"connect success %@",[weakSelf.device mac]);
        weakSelf.isConnecting = NO;
        [weakSelf fulfillCommandResponse:[AYBLEPacket packetWithCommand:AYBLECommandType_ConnnectState isTimeout:NO data:AYInt8Data(AYBLEDeviceConnectStatus_Success)]];
    }];
    [_bluetooth setBlockOnFailToConnect:^(CBCentralManager *central, CBPeripheral *peripheral, NSError *error) {
        AYBLELogger(1,@"connect failed %@",error.description);
        weakSelf.device = nil;
        [weakSelf fulfillCommandResponse:[AYBLEPacket packetWithCommand:AYBLECommandType_ConnnectState isTimeout:NO data:AYInt8Data(AYBLEDeviceConnectStatus_Failure)]];
    }];
    [_bluetooth setBlockOnDisconnect:^(CBCentralManager *central, CBPeripheral *peripheral, NSError *error) {
        NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
        userInfo[@"peripheral"] = peripheral;
        userInfo[@"error"] = error;
        AYBLELogger(1,@"disconnect %@ %@",error?error.description:@"normally", peripheral);
        if (weakSelf.disconnectStateBlock) weakSelf.disconnectStateBlock(error);
        weakSelf.disconnectStateBlock = nil;
        [[NSNotificationCenter defaultCenter] postNotificationName:blePeripheralDisconnectNotification object:nil userInfo:userInfo];
        [weakSelf resetData];
    }];
    
    [_bluetooth setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error) {
        for (CBCharacteristic *charc in service.characteristics) {
            if ([charc.UUID.UUIDString isEqualToString:weakSelf.readUUID]) {
                weakSelf.notifyCharc = charc;
                AYBLELogger(2,@"read characteristic:%@ properties:%zd - %@", charc.UUID.UUIDString,charc.properties, peripheral);
                [weakSelf.bluetooth notify:peripheral characteristic:charc block:^(CBPeripheral *peripheral, CBCharacteristic *characteristics, NSError *error) {
                    if (pthread_main_np()) {
                        [weakSelf notifyValueForCharacteristic:characteristics];
                    }else {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            [weakSelf notifyValueForCharacteristic:characteristics];
                        });
                    }
                }];
            }
            if ([charc.UUID.UUIDString isEqualToString:weakSelf.writeUUID]) {
                AYBLELogger(2,@"write characteristic:%@ properties:%zd - %@", charc.UUID.UUIDString,charc.properties, peripheral);
                weakSelf.writeCharc = charc;
            }
        }
    }];
    ///写入成功回调
    [_bluetooth setBlockOnDidWriteValueForCharacteristic:^(CBCharacteristic *characteristic, NSError *error) {
        AYBLELogger(2,@"%@ did write %@",characteristic.UUID.UUIDString, error?[NSString stringWithFormat:@"failure:[%@]",error.description]:@"success");
    }];
    ///读取回调
    [_bluetooth setBlockOnReadValueForCharacteristic:^(CBPeripheral *peripheral, CBCharacteristic *characteristic, NSError *error) {
        NSString *errorInfo = [NSString stringWithFormat:@"failure:[%@]",error.description];
        AYBLELogger(2,@"%@ did read %@",characteristic.UUID.UUIDString, error?errorInfo:@"success");
    }];
}

- (void)notifyValueForCharacteristic:(CBCharacteristic *)characteristic {
    if ([[characteristic value] length] == 0) return;
    if (![characteristic.UUID.UUIDString isEqualToString:self.readUUID]) return;
    NSData *data = [characteristic value];
    AYBLELogger(2,@"recv data:%@",[data ay_hex]);
    
    if (data.length < 12) {
        AYBLELogger(1,@"协议数据长度错误");
        return;
    }
    /// 同步码不一致
    if (B2H16(data.ay_int16) != self.syncCode) {
        [self resetPacketData];
        AYBLELogger(1,@"同步码错误:%@",AYDataFromLen(data, 0, 2).ay_hex);
        return;
    }
    /// 数据长度和字段里的值不一致
    if (B2H16([data ay_int16At:2]) != data.length) {
        [self resetPacketData];
        AYBLELogger(1,@"数据包长度错误 len:%d - len:%d",[data ay_int16At:2],(int)data.length);
        return;
    }
    uint16_t check_sum1 = CRC16_XMODEM(AYDataFromLen(data, 2, data.length - 4));
    uint16_t check_sum2 = B2H16([data ay_uint16At:data.length - 2]);
    /// CRC校验失败
    if (check_sum1 != check_sum2) {
        [self resetPacketData];
        NSString *sum1 = AYUInt16Data(check_sum1).ay_hex;
        NSString *sum2 = AYUInt16Data(check_sum2).ay_hex;
        AYBLELogger(1,@"校验错误 sum:%@ - sum:%@",sum1,sum2);
    }
    uint16_t current_len = B2H16([data ay_uint16At:4]);
    uint16_t total_len = B2H16([data ay_uint16At:6]);
    if (current_len > total_len) {
        [self resetPacketData];
        AYBLELogger(1,@"数据长度错误1 len:%d - len:%d",current_len,total_len);
        return;
    }
    uint16_t command = B2H16([data ay_uint16At:8]);
    NSData *msg = AYDataFromLen(data, 10, data.length - 12);
    NSData *message = nil;
    if (current_len == total_len) {
        message = msg;
        [self resetPacketData];
    } else {
        [self.packetData appendData:msg];
        if (self.packetData.length == total_len) {
            message = [self.packetData copy];
            [self resetPacketData];
        } else if (self.packetData.length > total_len) {
            AYBLELogger(1,@"数据长度错误2 len:%d - len:%d",(int)[self.packetData length],total_len);
            [self resetPacketData];
            return;
        }else {
            return;
        }
    }
    AYBLEPacket *packet = [AYBLEPacket packetWithCommand:command isTimeout:NO data:message];
    [self fulfillCommandResponse:packet];
}

- (void)resetPacketData {
    self.packetData.length = 0;
}

- (void)fulfillCommandResponse:(AYBLEPacket *)packet {
    AYBLECommandType command = packet.command;
    NSData *data = packet.data;
    BOOL isTimeout = packet.isTimeout;
    AYBLELogger(2,@"fulfill command:%04lX, data:%@, isTimeout:%d",(unsigned long)command,data?data.ay_hex:@"NULL",isTimeout);
    switch (command) {
        case AYBLECommandType_ConnnectState:
        {
            int8_t state = data.ay_int8;
            [[NSNotificationCenter defaultCenter] postNotificationName:blePeripheralConnectedNotification object:@(state)];
            if (self.connectStateBlock) {
                self.connectStateBlock(state);
                self.connectStateBlock = nil;
            }
        }
            break;
        case AYBLECommandType_SyncTime:
        {
            NSData *ts = AYUInt32Data([NSDate date].timeIntervalSince1970);
            NSData *tz = AYInt16Data([NSTimeZone systemTimeZone].secondsFromGMT/60);
            NSMutableData *dat = [NSMutableData dataWithData:ts];
            [dat appendData:tz];
            [self sendCommand:AYBLECommandType_SyncTime data:[dat copy]];
        }
            break;
        default:
            break;
    }
    /// 处理block回调
    if (command != AYBLECommandType_ConnnectState && self.commandResponses[@(command)]) {
        ((AYBLECommandResponseBlock)self.commandResponses[@(command)])(packet);
        self.commandResponses[@(command)] = nil;
    }
    [[NSNotificationCenter defaultCenter] postNotificationName:bleReceivedCommandDataNotification object:packet];
    if (self.commandTimers[@(command)]) {
        [self.commandTimers[@(command)] invalidate];
        self.commandTimers[@(command)] = nil;
    }
    if (isTimeout && command == AYBLECommandType_ConnnectState) {
        [self disconnect:self.device.peripheral];
    }
}

- (void)stopDiscover {
    [_bluetooth cancelScan];
}


- (void)startDiscover:(NSArray<NSString *> *)prefixes {
    self.prefixes = prefixes;
    [_bluetooth cancelAllPeripheralsConnection];
    _bluetooth.scanForPeripherals().begin();
}

- (void)connectPeripheral:(CBPeripheral *)peripheral { // 连接外设
    [_bluetooth cancelScan];
    self.isConnecting = YES;
    _bluetooth.having(peripheral).connectToPeripherals().discoverServices().discoverCharacteristics().begin();
}

- (void)connectWithDevice:(AYBLEDevice *)device timeout:(double)timeout connectStateBlock:(void (^)(AYBLEDeviceConnectStatus))connectStateBlock {
    self.device = device;
    self.isConnecting = YES;
    self.connectStateBlock = connectStateBlock;
    [self connectPeripheral:device.peripheral];
    if (timeout > 0) {
        [self.commandTimers[@(AYBLECommandType_ConnnectState)] invalidate];
        self.commandTimers[@(AYBLECommandType_ConnnectState)] = [AYBLETimer timerWithTimeInterval:timeout target:self selector:@selector(timerAction:) repeats:NO];
    }
}


- (void)connectWithMAC:(NSString *)mac timeout:(double)timeout connectStateBlock:(void (^)(AYBLEDeviceConnectStatus))connectStateBlock {
    if ([mac length]) {
        self.mac = mac;
        self.isConnecting = YES;
        self.connectStateBlock = connectStateBlock;
        [self startDiscover:@[@"ay_ipc"]];
        if (timeout > 0) {
            [self.commandTimers[@(AYBLECommandType_ConnnectState)] invalidate];
            self.commandTimers[@(AYBLECommandType_ConnnectState)] = [AYBLETimer timerWithTimeInterval:timeout target:self selector:@selector(timerAction:) repeats:NO];
        }
    }
}

- (void)resetData {
    [self.commandResponses removeAllObjects];
    [self.commandTimers removeAllObjects];
    self.device = nil;
    self.mac = nil;
    self.connectStateBlock = nil;
    self.disconnectStateBlock = nil;
    self.commandResponseBlock = nil;
    self.isConnecting = NO;
}

- (void)disconnect {
    if (self.device && self.notifyCharc) {
        [_bluetooth cancelNotify:self.device.peripheral characteristic:self.notifyCharc];
    }
    [_bluetooth cancelScan];
    [_bluetooth cancelAllPeripheralsConnection];
    [self resetData];
}

- (void)disconnect:(CBPeripheral *)peripheral {
    [_bluetooth cancelScan];
    if (self.device && self.notifyCharc) {
        [_bluetooth cancelNotify:self.device.peripheral characteristic:self.notifyCharc];
        [_bluetooth cancelPeripheralConnection:peripheral];
    }
    [self resetData];
}

///**************************************************************************
- (void)sendCommand:(AYBLECommandType)command {
    [self sendCommand:command data:nil timeout:0 completion:nil];
}
- (void)sendCommand:(AYBLECommandType)command timeout:(double)timeout {
    [self sendCommand:command data:nil timeout:timeout completion:nil];
}

///**************************************************************************
- (void)sendCommand:(AYBLECommandType)command data:(nullable NSData *)data {
    [self sendCommand:command data:data timeout:0 completion:nil];
}
- (void)sendCommand:(AYBLECommandType)command data:(NSData *)data timeout:(double)timeout {
    [self sendCommand:command data:data timeout:timeout completion:nil];
}

///**************************************************************************
- (void)sendCommand:(AYBLECommandType)command completion:(nullable AYBLECommandResponseBlock)completion {
    [self sendCommand:command data:nil timeout:0 completion:completion];
}
- (void)sendCommand:(AYBLECommandType)command timeout:(double)timeout completion:(nullable AYBLECommandResponseBlock)completion {
    [self sendCommand:command data:nil timeout:timeout completion:completion];
}

///**************************************************************************
- (void)sendCommand:(AYBLECommandType)command data:(nullable NSData *)data completion:(nullable AYBLECommandResponseBlock)completion {
    [self sendCommand:command data:data timeout:0 completion:completion];
}

- (void)_sendCommand:(AYBLECommandType)command data:(NSData *)data{
    _sendedCommand = command;
    if (self.device && self.writeCharc) {
        for (NSData *pkt in [self packWithData:data cmd:command]) {
            AYBLELogger(2,@"send command:%04lX, data:%@",(unsigned long)command, [pkt ay_hex]);
            [self.device.peripheral writeValue:pkt forCharacteristic:self.writeCharc type:CBCharacteristicWriteWithResponse];
        }
    }
}

- (void)sendCommand:(AYBLECommandType)command data:(NSData *)data timeout:(double)timeout completion:(void (^)(AYBLEPacket *packet))completion {
    AYBLELogger(2,@"send command prepare:%@ --- %d",self.device,self.isConnected);
    if (self.device && self.isConnected) {
        self.commandResponses[@(command)] = completion;
        [self _sendCommand:command data:data];
        if (timeout > 0) {
            [self.commandTimers[@(command)] invalidate];
            self.commandTimers[@(command)] = [AYBLETimer timerWithTimeInterval:timeout target:self selector:@selector(timerAction:) repeats:NO];
        }
    }
}

- (NSData *)xxteaEncrypt:(NSData *)data {
    const char *key = "com.cam.adorbee";
    char *encryped_data; size_t encrypty_len;
    encryped_data = ay_xxtea_encrypt(data.bytes, (int)data.length, key, &encrypty_len);
    NSData *encrypedData = [NSData dataWithBytes:encryped_data length:encrypty_len];
    free(encryped_data);
    return encrypedData;
}

- (NSData *)xxteaDecrypt:(NSData *)data {
    const char *key = "com.cam.adorbee";
    char *decryped_data; size_t decrypty_len;
    decryped_data = ay_xxtea_decrypt(data.bytes, (int)data.length, key, &decrypty_len);
    NSData *decrypedData = [NSData dataWithBytes:decryped_data length:decrypty_len];
    free(decryped_data);
    return decrypedData;
}

- (void)timerAction:(AYBLETimer *)timer {
    [self.commandTimers enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, AYBLETimer * _Nonnull obj, BOOL * _Nonnull stop) {
        if (obj == timer) {
            [self fulfillCommandResponse:[AYBLEPacket packetWithCommand:[key unsignedIntegerValue] isTimeout:YES data:AYInt8Data(AYBLEDeviceConnectStatus_Timeout)]];
            *stop = YES;
        }
    }];
}

- (NSArray<NSData *> *)packWithData:(NSData *)data cmd:(uint16_t)cmd {
    if (data.length == 0) {
        data = data?:[NSData data];
        return @[[self packWithData:data cmd:cmd totalLength:data.length]];
    }
    data = [self xxteaEncrypt:data]; /// < 数据加密
    NSUInteger length = data.length;
    NSMutableData *mData = [NSMutableData dataWithData:data];
    NSMutableArray *dataArray = [NSMutableArray array];
    while (mData.length >= 200) {
        NSRange range = NSMakeRange(0, 200);
        [dataArray addObject:[self packWithData:[mData subdataWithRange:range] cmd:cmd totalLength:length]];
        [mData replaceBytesInRange:range withBytes:NULL length:0];
    }
    if (mData.length) { /// 剩余长度
        [dataArray addObject:[self packWithData:[mData copy] cmd:cmd totalLength:length]];
    }
    return [dataArray copy];
}

///< 数据封装
- (NSData *)packWithData:(NSData *)data cmd:(uint16_t)cmd totalLength:(UInt16)totalLength {
    NSData *syncData = AYUInt16Data(H2B16(self.syncCode)); ///< 同步码数据
    NSData *pktLenData = AYUInt16Data(H2B16(data.length + 12)); ///< 数据包长度
    NSData *curLenData = AYUInt16Data(H2B16(data.length));
    NSData *totalLenData = AYUInt16Data(H2B16(totalLength));
    NSData *cmdData = AYUInt16Data(H2B16(cmd));
    NSData *checkSumData = AYDataArray(@[pktLenData,curLenData,totalLenData,cmdData,data]);
    uint16_t check_sum = H2B16(CRC16_XMODEM(checkSumData));
    NSMutableData *mData = [NSMutableData dataWithData:syncData];
    [mData appendData:checkSumData];
    [mData appendData:AYUInt16Data(check_sum)];
    return [mData copy];
}



NS_INLINE uint16_t CRC16_XMODEM(NSData *dat) {
    uint8_t *data = dat.bytes;
    uint16_t len = dat.length;
    uint16_t wCRCin = 0x0000;
    uint16_t wCPoly = 0x1021;
    for (int i = 0; i < len; i++) {
        for (int j = 0; j < 8; j++) {
            bool bit = (data[i] >> (7 - j) & 1) == 1;
            bool c15 = (wCRCin >> 15 & 1) == 1;
            wCRCin <<= 1;
            if (c15 != bit) {
                wCRCin ^= wCPoly;
            }
        }
    }
    wCRCin &= 0xffff;
    wCRCin ^= 0x0000;
    return wCRCin;
}

@end
